package org.ym.file.domain.model.node.embed;

import lombok.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.ym.basic.FileSnowflakeIdWorker;
import org.ym.file.domain.common.enums.FileStoreType;
import org.ym.file.domain.common.enums.FileStoreTypeConverter;
import org.ym.file.domain.common.model.*;
import org.ym.file.domain.model.node.common.download.FileUploadCommand;
import org.ym.file.domain.model.node.common.download.FileUploadFactory;
import org.ym.file.domain.model.node.common.download.FileUploadResult;
import org.ym.file.domain.model.node.common.upload.FileDownloadCommand;
import org.ym.file.domain.model.node.common.upload.FileDownloadFactory;
import org.ym.file.domain.model.node.common.upload.FileDownloadResult;
import org.ym.file.domain.model.node.exception.FileSliceDownloadException;
import org.ym.tools.*;

import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Embeddable;
import javax.persistence.Transient;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 每个节点最好单例运行
 *
 * @author ym
 */
@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@EqualsAndHashCode
@ToString
public class FileSystemWorkFolder implements Serializable {
    private static final long serialVersionUID = 7470969524090798700L;
    /**
     * 工作目录
     */
    @Column(name = "work_folder", length = 100, nullable = false)
    private String workFolder;
    /**
     * 文件存储规则
     */
    @Convert(converter = FileStoreTypeConverter.class)
    @Column(name = "file_store_type")
    private FileStoreType fileStoreType;

    /**
     * 是否有富裕空间
     *
     * @param fileByteSize 文件大小,byte
     * @return
     */
    private boolean isHasRichSpace(long fileByteSize) {
        switch (getFileStoreType()) {
            case NORMAL:
                return FileTool.isHasRichSpaceWithNoCompress(workFolder, fileByteSize);
            case ENCRYPTION:
                return FileTool.isHasRichSpaceWithCompress(workFolder, fileByteSize);
            default:
                return false;
        }
    }

    /**
     * 文件夹内最大存储文件及文件夹数量
     */
    @Transient
    private static final int FOLDER_MAX_FILE_LENGTH = 20000;
    /**
     * 一旦确定不允许更改
     */
    @Transient
    private static final String ENCRYPTION_PREFIX = "F_S_";
    /**
     * 一旦确定不允许更改
     */
    @Transient
    private static final String ENCRYPTION_SUFFIX = "FS";
    /**
     * 切片文件后缀
     */
    @Transient
    private static final String SLICE_SUFFIX = "SL";
    /**
     * 工作目录
     */
    @Transient
    private static final String FOLDER_FORMAL = "formal";
    /**
     * 工作目录索引
     */
    @Transient
    private AtomicInteger FOLDER_WORK_INDEX = new AtomicInteger();
    /**
     * 临时目录
     */
    @Transient
    private static final String FOLDER_TEMP = "temp";
    /**
     * 临时目录索引
     */
    @Transient
    private AtomicInteger FOLDER_TEMP_INDEX = new AtomicInteger();

    /**
     * 创建工作文件夹
     *
     * @return
     */
    private Path getStoreFolder(String workType) {
        Date currentDate = new Date();
        String year = DateTimeTool.formatDate(currentDate, DateTimeTool.FORMAT_YEAR);
        String monthDay = DateTimeTool.formatDate(currentDate, DateTimeTool.FORMAT_MONTH_DAY);
        Path dateFolderPath = Paths.get(workFolder, workType, year, monthDay);
        if (!dateFolderPath.toFile().exists()) {
            dateFolderPath.toFile().mkdirs();
        }
        synchronized (this) {
            AtomicInteger atomicInteger = null;
            if (FOLDER_FORMAL.equals(workType)) {
                atomicInteger = FOLDER_WORK_INDEX;
            } else {
                atomicInteger = FOLDER_TEMP_INDEX;
            }
            int index = atomicInteger.get();
            Path resultPath = null;
            String[] list;
            while (true) {
                resultPath = Paths.get(dateFolderPath.toString(), Integer.toString(index));
                if (!resultPath.toFile().exists()) {
                    resultPath.toFile().mkdirs();
                }
                list = resultPath.toFile().list();
                if (list == null || list.length < FOLDER_MAX_FILE_LENGTH) {
                    break;
                }
                index = atomicInteger.incrementAndGet();
            }
            return resultPath;
        }
    }

    /**
     * 创建工作空文件
     *
     * @param workType 工作目录类型
     * @param ext      文件类型
     * @return Path
     * @throws IOException 错误
     */
    private Path createFile(String workType, String ext) {
        synchronized (this) {
            Path folder = getStoreFolder(workType);
            Path filePath = Paths.get(folder.toString(), StringTool.concatStr(FileSnowflakeIdWorker.genId(), StringPool.DOT, ext));
            if (!filePath.toFile().exists()) {
                try {
                    filePath.toFile().createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return filePath;
        }
    }

    /**
     * 创建工作空文件夹
     *
     * @param workType 工作目录类型
     * @return Path
     */
    private Path createFolder(String workType) {
        synchronized (this) {
            Path folder = getStoreFolder(workType);
            Path createFolder = Paths.get(folder.toString(), FileSnowflakeIdWorker.genId());
            createFolder.toFile().mkdirs();
            return createFolder;
        }
    }

    /**
     * 创建临时工作目录
     *
     * @return 临时工作目录
     */
    public Path createEmptyTempFolder() {
        return createFolder(FOLDER_TEMP);
    }

    /**
     * 创建临时空工作文件
     *
     * @return 临时空工作文件
     */
    public Path createEmptyTempFile(String ext) {
        return createFile(FOLDER_TEMP, ext);
    }

    /**
     * 获取相对路径
     *
     * @param filePath 文件
     * @return
     */
    private String getRelativePath(Path filePath) {
        if (filePath != null) {
            return filePath.toString().replace(workFolder, "");
        } else {
            return "";
        }
    }

    /**
     * 获取实际存储路径
     *
     * @param relativePath 相对路径
     * @return 实际路径
     */
    private Path getRealPath(String relativePath) {
        if (StringUtils.isNotBlank(relativePath)) {
            return Paths.get(workFolder, relativePath);
        }
        return null;
    }

    /**
     * 上传文件
     *
     * @param source 文件流
     * @param ext    类型
     * @param nodeId 节点主键
     * @return 存储信息
     * @throws IOException 错误
     */
    private FileSaveInfo uploadFile(InputStream source, String ext, Long nodeId, String workType) throws IOException {
        Path filePath = null;
        try (InputStream inputStream = source) {
            filePath = createFile(workType, ext);
            long size = 0;
            String salt = null;
            String password = null;
            if (fileStoreType == FileStoreType.ENCRYPTION) {
                salt = FileSnowflakeIdWorker.genId();
                password = ENCRYPTION_PREFIX + salt;
            }
            FileUploadResult fileUploadResult = FileUploadFactory.getExecutor(fileStoreType)
                    .upload(source, FileUploadCommand.builder()
                            .filePath(filePath)
                            .password(password)
                            .newSuffix(ENCRYPTION_SUFFIX)
                            .build());
            String relativePath = getRelativePath(fileUploadResult.getFilePath());
            return FileSaveInfo.builder().size(size).nodeId(nodeId).salt(salt).fileStoreType(fileStoreType).savePath(relativePath).build();
        } catch (Exception e) {
            //报错删除文件,防止文件冗余产生
            FileTool.deleteFile(filePath);
            throw e;
        }
    }

    /**
     * 下载分解
     *
     * @param fileStoreType       文件存储类型
     * @param fileDownloadRequire 外部下载
     * @return 文件下载信息
     * @throws IOException
     */
    public FileDownloadInfo downloadFile(FileStoreType fileStoreType, FileDownloadRequire fileDownloadRequire) {
        Path filePath;
        Path fileDownloadPath = null;
        boolean isExists = false;
        long size = 0;
        if (StringUtils.isNotBlank(fileDownloadRequire.getFileRelativePath())) {
            filePath = getRealPath(fileDownloadRequire.getFileRelativePath());
            String password = "";
            if (fileStoreType == FileStoreType.ENCRYPTION) {
                password = ENCRYPTION_PREFIX + fileDownloadRequire.getSalt();
                fileDownloadPath = createFolder(FOLDER_TEMP);
            }
            FileDownloadResult fileDownloadResult = FileDownloadFactory.getExecutor(fileStoreType)
                    .download(FileDownloadCommand.builder().filePath(filePath).workFolderPath(fileDownloadPath).password(password).build());
            fileDownloadPath = fileDownloadResult.getFilePath();
            if (fileDownloadPath != null && fileDownloadPath.toFile().exists()) {
                isExists = true;
                size = FileUtils.sizeOf(fileDownloadPath.toFile());
            }
        }
        return FileDownloadInfo.builder().isExists(isExists).size(size).ext(fileDownloadRequire.getExt()).downloadPath(fileDownloadPath).build();
    }

    /**
     * 上传到临时目录中
     *
     * @param source 流
     * @param ext    文件类型
     * @param nodeId 节点
     * @return 存储信息
     * @throws IOException 错误
     */
    public FileSaveInfo uploadTempFile(InputStream source, String ext, Long nodeId) throws IOException {
        return uploadFile(source, ext, nodeId, FOLDER_TEMP);
    }

    /**
     * 上传到正式目录中
     *
     * @param source 流
     * @param ext    文件类型
     * @param nodeId 节点
     * @return 存储信息
     * @throws IOException 错误
     */
    public FileSaveInfo uploadFormalFile(InputStream source, String ext, Long nodeId) throws IOException {
        return uploadFile(source, ext, nodeId, FOLDER_FORMAL);
    }

    /**
     * 删除文件
     *
     * @param fileRelativePath 文件相对地址
     * @return true/false
     */
    public boolean deleteFile(String fileRelativePath) {
        if (StringUtils.isNotBlank(fileRelativePath)) {
            Path filePath = getRealPath(fileRelativePath);
            return FileTool.deleteFile(filePath);
        } else {
            return false;
        }
    }

    /**
     * 开启切片上传任务
     *
     * @param nodeId
     * @return
     */
    public FileSliceUploadTask startUploadSliceFile(Long nodeId) {
        return FileSliceUploadTask.builder().nodeId(nodeId).sliceWorkFolderPath(getRelativePath(createEmptyTempFolder())).build();
    }

    /**
     * 判断切片是否已经存在
     *
     * @param fileSliceUploadTask 切片任务
     * @return true/false
     */
    public boolean isExistSliceFile(FileSliceUploadTask fileSliceUploadTask) {
        if (fileSliceUploadTask != null) {
            Path workFolder = getRealPath(fileSliceUploadTask.getSliceWorkFolderPath());
            Path filePath = Paths.get(workFolder.toString(), StringTool.concatStr(String.valueOf(fileSliceUploadTask.getCurrentSliceIndex()), StringPool.DOT, SLICE_SUFFIX));
            return filePath.toFile().exists();
        }
        return false;
    }

    /**
     * 上传切片文件
     *
     * @param source              切片数据
     * @param fileSliceUploadTask 切片任务
     * @return 切片存储信息
     */
    public FileSliceUploadInfo uploadSliceFile(InputStream source, FileSliceUploadTask fileSliceUploadTask) {
        if (fileSliceUploadTask == null) {
            return FileSliceUploadInfo.builder().isSuccess(false).build();
        }
        Path filePath = null;
        try (InputStream inputStream = source) {
            long size = 0;
            Path workFolder = getRealPath(fileSliceUploadTask.getSliceWorkFolderPath());
            filePath = Paths.get(workFolder.toString(), StringTool.concatStr(String.valueOf(fileSliceUploadTask.getCurrentSliceIndex()), StringPool.DOT, SLICE_SUFFIX));
            if (!isExistSliceFile(fileSliceUploadTask)) {
                filePath.toFile().createNewFile();
                FileUploadResult fileUploadResult = FileUploadFactory.getExecutor(FileStoreType.NORMAL).upload(inputStream, FileUploadCommand.builder().filePath(filePath).build());
                size = fileUploadResult.getSize();
            } else {
                size = FileUtils.sizeOf(filePath.toFile());
            }
            return FileSliceUploadInfo.builder().isSuccess(true).sliceIndex(fileSliceUploadTask.getCurrentSliceIndex()).sliceSize(size).relativePath(fileSliceUploadTask.getSliceWorkFolderPath()).nodeId(fileSliceUploadTask.getNodeId()).build();
        } catch (Exception e) {
            FileTool.deleteFile(filePath);
            throw new RuntimeException(e);
        }
    }

    /**
     * 合并切片
     *
     * @param fileStoreType       存储类型
     * @param fileSliceUploadTask 切片任务
     * @return 存储信息
     */
    public FileSaveInfo commitUploadSliceFile(FileStoreType fileStoreType, FileSliceUploadTask fileSliceUploadTask) {
        Path filePath = null;
        if (fileSliceUploadTask != null) {
            Path workFolder = getRealPath(fileSliceUploadTask.getSliceWorkFolderPath());
            if (workFolder.toFile().exists()) {
                List<File> childFileList = Arrays.asList(workFolder.toFile().listFiles());
                childFileList.sort((f1, f2) -> {
                    int number1 = NumberTool.parseInt(f1.getName().substring(0, f1.getName().lastIndexOf(StringPool.DOT)));
                    int number2 = NumberTool.parseInt(f2.getName().substring(0, f2.getName().lastIndexOf(StringPool.DOT)));
                    return number1 - number2;
                });
                filePath = createFile(FOLDER_FORMAL, fileSliceUploadTask.getExt());
                try (FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile())) {
                    for (File file : childFileList) {
                        try (FileInputStream fileInputStream = new FileInputStream(file)) {
                            StreamTool.setOutPutStreamFromInputStream(fileOutputStream, fileInputStream);
                        } catch (FileNotFoundException e) {
                            throw new RuntimeException(e);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                } catch (FileNotFoundException e) {
                    FileTool.deleteFile(filePath);
                    throw new RuntimeException(e);
                } catch (IOException e) {
                    FileTool.deleteFile(filePath);
                    throw new RuntimeException(e);
                }
                String salt = null;
                String password = null;
                if (fileStoreType == FileStoreType.ENCRYPTION) {
                    salt = FileSnowflakeIdWorker.genId();
                    password = ENCRYPTION_PREFIX + salt;
                }
                FileUploadResult fileUploadResult = FileUploadFactory.getExecutor(fileStoreType).deal(FileUploadCommand.builder().filePath(filePath).newSuffix(ENCRYPTION_SUFFIX).password(password).build());
                return FileSaveInfo.builder().salt(salt).fileStoreType(fileStoreType).size(fileUploadResult.getSize()).savePath(getRelativePath(fileUploadResult.getFilePath())).nodeId(fileSliceUploadTask.getNodeId()).build();
            }
        }
        return FileSaveInfo.builder().build();
    }

    /**
     * 开启分片下载任务
     *
     * @param fileStoreType       存储类型
     * @param fileDownloadRequire 下载请求
     * @return
     */
    public FileSliceDownloadTask startDownloadSliceTask(FileStoreType fileStoreType, FileDownloadRequire fileDownloadRequire) {
        FileDownloadInfo fileDownloadInfo = downloadFile(fileStoreType, fileDownloadRequire);
        return FileSliceDownloadTask.builder()
                .nodeId(fileDownloadRequire.getNodeId())
                .totalSize(fileDownloadInfo.getSize())
                .sliceWorkFolderPath(getRelativePath(fileDownloadInfo.getDownloadPath()))
                .ext(fileDownloadRequire.getExt())
                .build();
    }

    /**
     * 下载切片
     *
     * @param fileSliceDownloadTask 切片命令
     * @return 切片信息数据
     */
    public FileSliceDownloadInfo downloadSliceFile(FileSliceDownloadTask fileSliceDownloadTask) {
        if (fileSliceDownloadTask != null) {
            Path filePath = getRealPath(fileSliceDownloadTask.getSliceWorkFolderPath());
            if (filePath.toFile().exists()) {
                long realSize = FileUtils.sizeOf(filePath.toFile());
                if (realSize != fileSliceDownloadTask.getTotalSize()) {
                    throw new FileSliceDownloadException("切片命令文件实际大小与真实文件大小不匹配！");
                }
                if (fileSliceDownloadTask.getStartIndex() > fileSliceDownloadTask.getEndIndex()) {
                    throw new IndexOutOfBoundsException(StringTool.concatStr("文件切片起始位置不能大于结束位置！"));
                }
                if (fileSliceDownloadTask.getStartIndex() > fileSliceDownloadTask.getTotalSize()) {
                    throw new IndexOutOfBoundsException(StringTool.concatStr("文件切片起始位置不能大于文件实际大小！"));
                }
                if (fileSliceDownloadTask.getEndIndex() > fileSliceDownloadTask.getTotalSize()) {
                    throw new IndexOutOfBoundsException(StringTool.concatStr("文件切片结束位置不能大于文件实际大小！"));
                }
                int length = (int) (fileSliceDownloadTask.getEndIndex() - fileSliceDownloadTask.getStartIndex() + 1);
                byte[] data = new byte[length];
                try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r")) {
                    randomAccessFile.seek(fileSliceDownloadTask.getStartIndex());
                    randomAccessFile.read(data);
                } catch (FileNotFoundException e) {
                    throw new RuntimeException(e);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                return FileSliceDownloadInfo.builder()
                        .totalSize(fileSliceDownloadTask.getTotalSize())
                        .startIndex(fileSliceDownloadTask.getStartIndex())
                        .data(data)
                        .endIndex(fileSliceDownloadTask.getEndIndex())
                        .ext(fileSliceDownloadTask.getExt())
                        .sliceWorkFolderPath(fileSliceDownloadTask.getSliceWorkFolderPath())
                        .nodeId(fileSliceDownloadTask.getNodeId())
                        .build();
            }
        }
        return FileSliceDownloadInfo.builder()
                .nodeId(fileSliceDownloadTask.getNodeId())
                .sliceWorkFolderPath(fileSliceDownloadTask.getSliceWorkFolderPath())
                .startIndex(fileSliceDownloadTask.getStartIndex())
                .endIndex(fileSliceDownloadTask.getEndIndex())
                .ext(fileSliceDownloadTask.getExt())
                .totalSize(fileSliceDownloadTask.getTotalSize())
                .build();
    }
}
